package org.six11.ss.server;

import java.io.PrintWriter;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.jdom.Element;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import org.six11.ss.server.bean.Game;
import org.six11.ss.server.bean.Person;
import org.six11.ss.server.bean.Room;
import org.six11.ss.server.bean.Round;
import org.six11.ss.server.bean.Sketch;
import org.six11.ss.shared.Message;
import org.six11.util.adt.MultiState;
import org.six11.util.adt.SynchronizedQueue;

/**
 * 
 * 
 * @author Gabe Johnson <johnsogg@cmu.edu>
 */
public class PenHandler extends AppletMessageHandler {

  private XMLOutputter xmlOut;

  private static long start_to_end;
  private static long start_to_end_n;

  private static long t_setup;
  private static long t_setup_n;

  private static long t_route;
  private static long t_route_n;

  private static long t_record_buffer = 0;
  private static long t_record_buffer_n = 0;

  /**
   * @param state
   */
  public PenHandler(MultiState state) {
    super(state);
    xmlOut = new XMLOutputter(Format.getPrettyFormat());
  }

  private long now() {
    return System.currentTimeMillis();
  }

  /**
   * Receives pen data, distributes it to everybody in the room except the sender, and adds the data
   * to the sketch buffer.
   */
  @SuppressWarnings( {
      "null", "unchecked"
  })
  @Override
  public void handle(Message m, String sessionID, PrintWriter out) {
    long start = 0;
    long end_setup = 0;
    long end_routing = 0;
    long end_record_buffer = 0;
    long end = 0;
    start = now();
    try {
      // get the Person
      Person person = getPersonBySession(sessionID);
      if (person == null) {
        error("I don't have that Person object. Are you logged in?");
      }

      // get the Room
      Room room = person.getRoom();
      if (room == null) {
        error("The person is not in a Room. This should never happen!");
      }

      // get the Game
      Game game = room.getCurrentGame();
      if (game == null) {
        error("The person is not playing a game, so there isn't a context to put sketch messages.");
      }

      Round round = game.getCurrentRound();
      if (round == null) {
        error("The current game doesn't have a round associated with it.");
      }
      // get the current sketch
      Sketch sketch = round.getSketch();
      if (sketch == null) {
        error("The current round does not have a sketch canvas at the moment.");
      }
      m.addParam("personID", person.getId() + "");
      end_setup = now();

      Set<Person> players = sketch.getRound().getGame().getRoom().getPlayers();
      for (Person p : players) {
        if (p.getId() != person.getId()) { // don't send sketch data back to sender.
          SynchronizedQueue<Message> q = getMessageQueue(p);
          q.add(m.copy());
        }
      }
      end_routing = now();

      StringBuilder sketchBuffer = getSketchBuffer(sketch.getId() + "");
      List penElements = m.getRoot().getChildren("pen_data");
      for (Object penObj : penElements) {
        Element penElm = (Element) penObj;
        penElm.setAttribute("personID", person.getId() + "");
        String thisMsg = xmlOut.outputString(penElm);
        sketchBuffer.append(thisMsg + "\n");
      }
      end_record_buffer = now();
    } catch (Exception ex) {
      ex.printStackTrace();
    }
    end = now();

    start_to_end += end - start;
    start_to_end_n++;

    t_setup += end_setup - start;
    t_setup_n++;

    t_route += end_routing - end_setup;
    t_route_n++;

    t_record_buffer += end - end_record_buffer;
    t_record_buffer_n++;
  }

  @SuppressWarnings("unchecked")
  public StringBuilder getSketchBuffer(String sketchID) {
    Map<String, StringBuilder> buffers = (Map<String, StringBuilder>) state
        .getValue(ServerStateNames.SKETCH_BUFFERS);
    if (!buffers.containsKey(sketchID)) {
      StringBuilder sb = new StringBuilder();
      buffers.put(sketchID, sb);
    }
    return buffers.get(sketchID);
  }

  /**
   * Find the current sketch buffer for the current room and saves it to the database. The format is
   * a "stellasketch" xml document, which has a root element named "stellasketch" and a bunch of
   * in-order "pen_data" elements. Other element types (e.g. setting pen colors or thicknesses, or
   * erasing things) may also be in there, but I haven't designed that yet.
   * 
   * @param roomID
   */
  protected void saveSketchBuffer(String roomID) {
    try {
      Room room = loadRoom(roomID);
      if (room == null) {
        error("Room unknown: " + roomID);
      }
      Game game = room.getCurrentGame();
      if (game == null) {
        error("Game null for room: + roomID");
      }
      Round round = game.getCurrentRound();
      if (round == null) {
        error("Round null for room: " + room.getId() + " in game " + game.getId());
      }
      Sketch sketch = round.getSketch();
      if (sketch == null) {
        error("Sketch null for room: " + room.getId() + " in game " + game.getId() + " in round "
            + round.getId());
      }
      StringBuilder sb = getSketchBuffer("" + sketch.getId());

      if (sb.length() == 0) {
        error("Refusing to save a sketch with zero bytes content.");
      } else {
        log("Saving sketch " + sketch.getId() + "'s data to the database ("
            + sb.toString().length() + " characters).");
        sketch.setData("<msg type=\"pen\">\n" + sb.toString() + "</msg>\n");
      }
    } catch (Exception ex) {
      log(ex.getMessage());
      ex.printStackTrace();
    }

    if (start_to_end_n > 0) {
      bug("Average pen handle times: ");
      bug("\tstart to end: " + ((double) start_to_end / (double) start_to_end_n) + " ("
          + start_to_end_n + " instances)");
    }

  }

}
